// The MIT License

// Copyright (c) 2009 Massachusetts Institute of Technology

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Connectable::*;
import GetPut::*;
import ClientServer::*;
//wjun
import FIFO::*;
import FIFOF::*;
import BRAMFIFO::*;
import RegFile::*;

//AWB includes
`include "asim/provides/low_level_platform_interface.bsh"
`include "asim/provides/soft_connections.bsh"
`include "asim/provides/common_services.bsh"

// Local includes
`include "asim/provides/processor_library.bsh"
`include "asim/provides/mem_arb.bsh"
`include "asim/provides/instruction_cache.bsh"
`include "asim/provides/data_cache.bsh"
`include "asim/provides/processor.bsh"

// wjun
// This is located here and not int ProcessorSystem because
// It would work better if instantiated inside mkCore
interface Decompressor;
	interface Put#(RunRes) run_result;
	interface Get#(StepRes) state_feed;


	// step
	interface Put#(StepRes) put_compressed;
	interface Get#(StepRes) get_decompressed;

	method Bool ispredicting;
	method Action add_compressedQ_count;
	method Bit#(16) get_compressedQ_count;
endinterface

module mkDecompressor( Decompressor );
	FIFO#(RunRes) runResQ <- mkFIFO();
	RegFile#(Bit#(5),Bit#(32)) sreg <- mkRegFileFull();
	Reg#(Bool) predicting <- mkReg(False);
	
	FIFO#(StepRes) comprQ <- mkSizedBRAMFIFO(64);
	Reg#(Bit#(16)) compressedQ_count <- mkReg(64);
	FIFO#(StepRes) decomprQ <- mkSizedBRAMFIFO(64);
//	FIFO#(StepRes) comprQ <- mkSizedFIFO(512+256);
//	Reg#(Bit#(16)) compressedQ_count <- mkReg(512+256);
//	FIFO#(StepRes) decomprQ <- mkSizedFIFO(512);
	FIFO#(StepRes) statefeedQ <- mkFIFO();
	Reg#(Maybe#(StepRes)) temp_feed <- mkReg(Invalid);

	rule loadcompressed(!predicting);

		if ( isValid(temp_feed) )
		begin
			let t = fromMaybe(?,temp_feed);
			statefeedQ.enq(t);
			decomprQ.enq(t);
			if (t.regChange)
				sreg.upd(t.target, t.value);
			temp_feed <= tagged Invalid;
		end
		else begin
			compressedQ_count <= compressedQ_count + 1;
			comprQ.deq();
			let c = comprQ.first();
		//	temp_feed <= tagged Valid c;

			if (c.longMatch)
			begin
				predicting <= True;
				statefeedQ.enq(c);
			end 
			else if ( c.matchcount > 0 )
			begin
				predicting <= True;
				statefeedQ.enq(StepRes{
					matchcount:c.matchcount,
					regChange:False,
					memAcc:0,
					size:0,
					target:0,
					done:False,
					longMatch:False,
					longCount:0,

					pc:0,
					addr:0,
					value:0
				});
				temp_feed <= tagged Valid StepRes {
					matchcount:0,
					regChange:c.regChange,
					memAcc:c.memAcc,
					size:c.size,
					target:c.target,
					done:False,
					longMatch:False,
					longCount:0,

					pc:c.pc,
					addr:c.addr,
					value:c.value
				};
			end
			else begin
				if (c.regChange)
					sreg.upd(c.target, c.value);
				decomprQ.enq(c);
			end
		end
	/*	
		if (c.matchcount > 0 )
		begin
			predicting <= True;
			$display("starting prediction!(%x)", c.matchcount);
		end
		else
		begin
			if (c.regChange)
				sreg.upd(c.target, c.value);

			decomprQ.enq(c);
		end
		
		statefeedQ.enq(c);
		*/
	endrule


	Reg#(Bool) regChange <- mkReg(False);
	Reg#(Bit#(32)) value <- mkReg(0);
	Reg#(Bit#(32)) addr <- mkReg(0);
	Reg#(Bit#(2)) memAcc <- mkReg(memAccNone);
	Reg#(Bit#(5)) target <- mkReg(0);
	Reg#(Bit#(32)) pc <- mkReg(0);
	rule runres(predicting);
		//TODO: add regfile to check which regs changed
		//TODO: enQ results
		runResQ.deq();
		let r = runResQ.first();

		if (r.traceType == doneVal)
		begin
//			$display ("ending prediction!");
			predicting <= False;

			regChange <= False;
			value <= 0;
			memAcc <= memAccNone;
			target <= 0;
			pc <= 0;
			// everything else but 'done' is ignored, but left there for debugging purposes
			decomprQ.enq(StepRes{
				matchcount:0,
				regChange:regChange,
				memAcc:memAcc,
				size:4,
				target:target,
				done:False,
				longMatch:False,
				longCount:0,

				pc:pc,
				addr:addr,
				value:value
			});
		end
		else 
		begin
			if ( r.traceType == regVal && sreg.sub(r.target) != r.value2)
				begin
					regChange <= True;
					value <= r.value1;
					target <= r.target;
					sreg.upd(r.target, r.value1);
				end
			if (r.traceType == pcVal )
				begin
					pc <= r.value1;
					decomprQ.enq(StepRes{
						matchcount:0,
						regChange:regChange,
						memAcc:memAcc,
						size:4,
						target:target,
						done:False,
						longMatch:False,
						longCount:0,

						pc:r.value1,
						addr:addr,
						value:value
					});

					regChange <= False;
					value <= 0;
					memAcc <= memAccNone;
					target <= 0;
				end
			if (r.traceType == memReadVal)
				begin
					value <= r.value2;
					memAcc <= memAccRead;
					addr <= r.value1;
					if (sreg.sub(r.target) == r.value2)
					begin
						regChange <= False;
					end
					else
					begin
						regChange <= True;
						sreg.upd(r.target, r.value2);
						target <= r.target;
					end
				end
			if (r.traceType == memWriteVal)
				begin
					regChange <= False;
					memAcc <= memAccWrite;
					addr <= r.value1;
					value <= r.value2;
				end
		end
		//	$display ("res %x[%x:%x] %x %x", runResQ.first().traceType, runResQ.first().target, runResQ.first().size, runResQ.first().value1, runResQ.first().value2);
	endrule
	
	interface Put run_result = fifoToPut(runResQ);
	interface Get state_feed = fifoToGet(statefeedQ);
	interface Put put_compressed = fifoToPut(comprQ);
	interface Get get_decompressed = fifoToGet(decomprQ);
	method Bool ispredicting ();
		return predicting;
	endmethod
	method Action add_compressedQ_count();
		compressedQ_count <= compressedQ_count - 1;
	endmethod
	method Bit#(16) get_compressedQ_count();
		return compressedQ_count;
	endmethod
endmodule

interface Core;

  // Interface from core to main memory
//  interface Client#(MainMemReq,MainMemResp) mmem_client;

  // wjun - instruction fetch...
  interface Client#(MainMemReq,MainMemResp) imem_client;
	interface Put#(StepRes) put_compressed;
	interface Get#(StepRes) get_decompressed;
	method Bool ispredicting;
	method Action add_compressedQ_count;
	method Bit#(16) get_compressedQ_count;
	method Bit#(32) get_proc_data;
endinterface

module   [CONNECTED_MODULE]  mkCore( Core );

  // Instantiate the modules

  Proc proc <- mkProc();
  ICache#(InstReq,InstResp) icache <- mkInstCache();
  DCache#(DataReq,DataResp) dcache <- mkDataCache();
  MemArb marb <- mkMemArb();
	// wjun
	Decompressor decompressor <- mkDecompressor();

  // Internal connections

//  mkConnection( proc.statsEn_get,   icache.statsEn_put );
//  mkConnection( proc.statsEn_get,   dcache.statsEn_put );

  // wjun - connect to instruction fetch fifo
	// TODO set icache back up, after everything works correctly, to speed things up
	mkConnection( decompressor.run_result, proc.run_result );
//	mkConnection( decompressor.feed_compressed, proc.feed_compressed );
	mkConnection( decompressor.state_feed, proc.state_feed );

//  mkConnection( proc.imem_client, marb.imem_server );

	mkConnection( proc.imem_client, icache.proc_server );
	mkConnection( icache.mmem_client, marb.imem_server );

  mkConnection( proc.dmem_client,   dcache.proc_server );
//  mkConnection( icache.mmem_client, marb.cache0_server );
//  mkConnection( dcache.mmem_client, marb.cache1_server ); //doesn't exist anymore

  // Methods
	method Bool ispredicting ();
		return decompressor.ispredicting();
	endmethod

	method Action add_compressedQ_count();
		decompressor.add_compressedQ_count();
	endmethod
	method Bit#(16) get_compressedQ_count();
		return decompressor.get_compressedQ_count();
	endmethod
	method Bit#(32) get_proc_data();
		return proc.procdata_get();
	endmethod

  interface imem_client = marb.imem_client;
	interface put_compressed = decompressor.put_compressed;
	interface get_decompressed = decompressor.get_decompressed;
endmodule
